package com.hzw.saas.web.storage.controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;

import com.hzw.saas.api.storage.IRsyncService;
import com.hzw.saas.api.storage.bo.*;
import com.hzw.saas.api.storage.enums.ResourceAuthEnum;
import com.hzw.saas.common.config.annotation.SysLog;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.config.util.AssertUtil;
import com.hzw.saas.common.util.HttpContextUtils;
import com.hzw.saas.service.storage.annotation.FileAuthBefore;
import com.hzw.saas.web.storage.param.ClientDiffBlockDataParam;
import com.hzw.saas.web.storage.param.ServerOriginBlocksListParam;
import com.hzw.saas.web.storage.param.ServerOriginBlocksListParam2;
import cn.hutool.extra.servlet.ServletUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 文件同步流程(C=client, S=server，vid=文件版本线，同一文件的不同版本vid相同，pid不同)
 * <p>
 * 1. C -> S 获取文件信息列表
 * 2. C 对比本地文件与获取的文件信息列表
 * 2.1. C 本地与服务器文件版本信息一致（vid相同，pid相同）（即服务器文件无变动）
 * 2.1.1 C 检查本地文件是否变化(比对强弱校验和)，若有变化则进行 C -> S 同步
 * 2.2. C 本地与服务器文件版本信息不一致（vid相同，pid不同）
 * 2.2.1. C -> S 请求当前文件详细信息(C端对应版本的文件信息和最新版本的文件信息)
 * 2.2.2. C 本地文件无变动（本地文件校验和==C端对应版本文件信中的息校验和）， S -> C 同步流程
 * 2.2.3. C 本地文件有变动 （本地文件校验和!=C端对应版本文件信息中的校验和），备份本地文件，S -> C同步流程，对比文件(可选)
 * 2.3. C 获取到的文件vid无本地文件相匹配，下载
 * 2.4. C 本地文件vid无获取到的文件相匹配，本地删除（或备份）
 * <p>
 * 3. C -> S 同步流程
 * 3.1. C -> S 拉取rsync同步块信息
 * 3.2. C 滚动对比，生成差异块列表
 * 3.3. C -> S 上传差异块
 * 3.4. C -> S 发送合并请求，携带差异块信息列表
 * <p>
 * 4. S -> C 同步流程
 * 4.1. C -> S 发送本地rsync同步块信息，服务器返回差异块信息
 * 4.2. C -> S 下载差异块
 * 4.3. C 根据差异块信息生成新文件
 *
 * @author EZ09
 * @since 07/07/2021
 */
@Controller
@Validated
@RequiredArgsConstructor
@Slf4j
@Api(tags = "资源管理/文件同步")
public class UserResourceSyncController {

    private final IRsyncService rsyncService;

    private static final String RSYNC_MANUAL = "文件同步流程(C=client, S=server，vid=文件版本线，同一文件的不同版本vid相同，pid不同)\n" +
        "\n" +
        "1. C -> S 获取文件信息列表\n" +
        "2. C 对比本地文件与获取的文件信息列表\n" +
        "2.1. C 本地与服务器文件版本信息一致（vid相同，pid相同）（即服务器文件无变动）\n" +
        "2.1.1 C 检查本地文件是否变化(比对强弱校验和)，若有变化则进行 C -> S 同步\n" +
        "2.2. C 本地与服务器文件版本信息不一致（vid相同，pid不同）\n" +
        "2.2.1. C -> S 请求当前文件详细信息(可选)(C端对应版本的文件信息和最新版本的文件信息)\n" +
        "2.2.2. C 本地文件无变动（本地文件校验和==C端对应版本文件信中的息校验和）， S -> C 同步流程\n" +
        "2.2.3. C 本地文件有变动 （本地文件校验和!=C端对应版本文件信息中的校验和），备份本地文件，S -> C同步流程，对比文件(可选)\n" +
        "\n" +
        "3. C -> S 同步流程\n" +
        "3.1. C -> S 拉取rsync同步块信息\n" +
        "3.2. C 滚动对比，生成差异块列表\n" +
        "3.3. C -> S 上传差异块\n" +
        "3.4. C -> S 发送合并请求，携带差异块信息列表\n" +
        "\n" +
        "4. S -> C 同步流程\n" +
        "4.1. C -> S 发送本地rsync同步块信息，服务器返回差异块信息\n" +
        "4.2. C -> S 下载差异块\n" +
        "4.3. C 根据差异块信息生成新文件";

    @GetMapping("/sync/manual/")
    @ApiOperation(value = "Rsync同步说明", notes = RSYNC_MANUAL)
    @ApiOperationSort(11)
    public ResponseEntity<String> getRsyncManual() {
        return ResponseEntity.ok(RSYNC_MANUAL);
    }

    // @GetMapping("/sync/file/{fileId}/version")
    // @FileAuthBefore(fileId = "#fileId", auth = ResourceAuthEnum.OWN)
    // @SysLog
    // @ApiOperation(value = "获取文件版本信息(可选)", notes = "获取文件详细信息，包含fileId对应的文件信息和fileId相关联最新版的文件信息，若客户端会保存详细的文件信息或可以判断出本地文件是否变化，这一步可忽略")
    // @ApiImplicitParams({
    //     @ApiImplicitParam(name = "fileId", value = "客户端当前文件ID", dataType = "String", required = true)
    // })
    // @ApiResponses({
    //     @ApiResponse(code = 200, message = "请求成功"),
    //     @ApiResponse(code = 400, message = "请求参数异常"),
    //     @ApiResponse(code = 503, message = "最新版本文件正在合并"),
    // })
    // @ApiOperationSort(12)
    public ResponseEntity<SyncVersionFileBO> getVersionInfo(@PathVariable String fileId) {
        SyncVersionFileBO versionInfo = rsyncService.getVersionInfo(fileId);
        return ResponseEntity.ok(versionInfo);
    }

    @GetMapping("/sync/file/{fileId}/origin-blocks")
    @FileAuthBefore(fileId = "#fileId", auth = ResourceAuthEnum.OWN)
    @SysLog
    @ApiOperation(value = "获取服务端文件对比块列表", notes = "获取服务端文件对比块列表，更新服务端文件前置")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "fileId", value = "文件ID（需最新版本）", dataType = "String", required = true)
    })
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(13)
    public ResponseEntity<ServerOriginFileBO> listServerOriginBlocks(@PathVariable String fileId, @Validated ServerOriginBlocksListParam param) {
        if (Objects.isNull(param.getBlockStep()))
            param.setBlockStep(param.getBlockStep());
        if (Objects.isNull(param.getChecksumMode()))
            param.setChecksumMode(0);
        ServerOriginFileBO serverOriginFile = rsyncService.listServerOriginBlocks(fileId, param.getBlockLength(), param.getBlockStep(), param.getChecksumMode());
        return ResponseEntity.ok(serverOriginFile);
    }

    @GetMapping("/sync/{syncId}/file/origin-block")
    @FileAuthBefore(fileId = "#syncId", auth = ResourceAuthEnum.OWN)
    @SysLog
    @ApiOperation(value = "获取服务端文件指定位置的对比块信息列表", notes = "获取服务端文件指定位置的对比块信息列表，需要syncId(即先调用<获取服务端文件对比块列表>接口),一般用于获取强校验和")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "syncId", value = "同步任务ID", dataType = "String", required = true)
    })
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 410, message = "同步任务失效/不存在"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(13)
    public ResponseEntity<List<OriginBlockBO>> listServerOriginBlocks2(@PathVariable String syncId, @Validated ServerOriginBlocksListParam2 param) {
        List<OriginBlockBO> originBlockBOList = rsyncService.listServerOriginBlocks2(syncId, param.getBlockPosList(), param.getChecksumMode());
        return ResponseEntity.ok(originBlockBOList);
    }

    @PostMapping("/sync/file/diff-blocks")
    @FileAuthBefore(syncId = "#param.syncId", auth = ResourceAuthEnum.OWN)
    @SysLog(operation = "提交客户端文件差异块列表", mode = 3)
    @ApiOperation(value = "提交客户端文件差异块列表", notes = "提交客户端文件差异块列表，用于更新服务端文件")
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 410, message = "同步任务失效/不存在"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(14)
    public ResponseEntity<ResourceFileBO> receiveClientDiffBlocks(@Validated @RequestBody ClientDiffFileBO param) {
        ResourceFileBO fileBO = rsyncService.receiveClientDiffBlocks(param);
        return ResponseEntity.ok(fileBO);
    }

    @GetMapping("/sync/file/diff-blocks")
    @FileAuthBefore(fileId = "#param.fileId", auth = ResourceAuthEnum.OWN)
    @SysLog
    @ApiOperation(value = "提交客户端文件对比块列表，并获取服务端文件差异块列表", notes = "提交客户端文件对比块列表，获取服务端文件差异块列表，用于更新客户端文件")
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(15)
    public ResponseEntity<ServerDiffFileBO> listServerDiffBlocks(@Validated @RequestBody ClientOriginFileBO param) {
        ServerDiffFileBO fileDiffServerBO = rsyncService.listServerDiffBlocks(param);
        return ResponseEntity.ok(fileDiffServerBO);
    }

    @PostMapping(value = "/sync/file/diff-block", consumes = {"multipart/form-data"})
    @SysLog
    @ApiOperation(value = "上传客户端文件差异块", notes = "上传客户端文件差异块，用于更新服务端文件")
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数或请求类型异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 410, message = "同步任务失效/不存在"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(16)
    public ResponseEntity<Void> saveClientDiffBlockData(@Validated ClientDiffBlockDataParam param, HttpServletRequest request) {
        // 判断请求类型
        boolean multipart = ServletUtil.isMultipart(request);
        AssertUtil.assertThrow("上传失败，请求参数异常，请使用multipart/form-data类型上传", !multipart);
        // 保存
        try (InputStream ins = param.getBlockFile().getInputStream()) {
            rsyncService.saveClientDiffBlockData(param.getSyncId(), param.getBlockId(), ins);
        } catch (IOException e) {
            throw DefaultSaasException.build("分块上传失败", e);
        }
        return ResponseEntity.ok().build();
    }

    @GetMapping("/sync/file/{fileId}")
    @FileAuthBefore(fileId = "#fileId", auth = ResourceAuthEnum.READ)
    @SysLog
    @ApiOperation(value = "下载服务端文件差异块", notes = "下载服务端分块，用于更新客户端文件")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "access_token", value = "用户token（或在请求头设置Authorization）", dataType = "String", paramType = "query", required = false),
        @ApiImplicitParam(name = "fileId", value = "资源文件ID", dataType = "String", paramType = "path", required = true),
        @ApiImplicitParam(name = "blockPos", value = "分块在文件中偏移量，从0开始", dataType = "Int", paramType = "query", required = true),
        @ApiImplicitParam(name = "blockLength", value = "分块长度", dataType = "Int", paramType = "query", required = true)
    })
    @ApiResponses({
        @ApiResponse(code = 200, message = "请求成功"),
        @ApiResponse(code = 400, message = "请求参数异常"),
        @ApiResponse(code = 404, message = "文件不存在/文件已过期"),
        @ApiResponse(code = 423, message = "文件正在合并")
    })
    @ApiOperationSort(17)
    public void getServerDiffBlockData(@PathVariable String fileId, @NotNull(message = "block pos不可为空") Integer blockPos,
                                       @NotNull(message = "block length不可为空") Integer blockLength,
                                       HttpServletRequest request, HttpServletResponse response) {
        try (OutputStream fos = response.getOutputStream()) {
            HttpContextUtils.setFileDownloadHeader(request, response, blockPos.toString().concat("@").concat(blockLength.toString()), blockLength);
            rsyncService.getServerDiffBlockData(fileId, blockPos, blockLength, fos);
            response.flushBuffer();
            log.debug("download block over...");
        } catch (IOException e) {
            log.error("download block error", e);
            throw DefaultSaasException.build("下载文件异常");
        }
    }

}
